cmake_minimum_required(VERSION 3.18 FATAL_ERROR)

project(ToolChain)
set(CMAKE_FOLDER "ToolChain")

if (CMAKE_CROSSCOMPILING)
  set(PRCC_COMMAND "${CMAKE_SOURCE_DIR}/bin/prcc${CMAKE_EXECUTABLE_SUFFIX}" PARENT_SCOPE)
  set(CODEGEN_COMMAND "${CMAKE_SOURCE_DIR}/bin/codegen${CMAKE_EXECUTABLE_SUFFIX}" PARENT_SCOPE)
else()
  set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/bin/")
  add_subdirectory(prcc)
  set(PRCC_COMMAND prcc PARENT_SCOPE)
  add_subdirectory(codegen)
  set(CODEGEN_COMMAND codegen PARENT_SCOPE)
endif()

# 资源使用的 property
define_property(TARGET PROPERTY RESOURCE_INIT_DECLARES)

function(add_resource TARGET ROOT SUB_DIR)

  # message(STATUS "PRCC is ${PRCC_COMMAND}")
  get_filename_component(FOLDER_NAME ${ROOT} NAME)
  string(REPLACE " " "_" FOLDER_NAME ${FOLDER_NAME})

  get_target_property(BIN_DIR ${TARGET} BINARY_DIR)
  get_target_property(CURRENT_DECLARES ${TARGET} RESOURCE_INIT_DECLARES)

  # 1. 创建资源子目录 <BIN_DIR>/<FOLDER_NAME>/<SUB_DIR>
  set(RESOURCE_DIR ${BIN_DIR}/${FOLDER_NAME}/${SUB_DIR})
  file(MAKE_DIRECTORY ${RESOURCE_DIR})

  # 2. 包含此目录
  target_include_directories(${TARGET} PRIVATE ${RESOURCE_DIR})

  # 3. 创建头文件 和 空白源文件，顺便填写资源清单
  file(GLOB_RECURSE RES_FILES ${ROOT}/${SUB_DIR}/*.*)
  set(HEADER_FILE ${RESOURCE_DIR}/resource_${SUB_DIR}.hpp) # 变量定义
  set(INIT_FILE   ${RESOURCE_DIR}/resource_${SUB_DIR}.cpp) # 目录初始化函数
  set(LIST_FILE   ${RESOURCE_DIR}/${SUB_DIR}.list)         # 资源文件列表

  ## 3.1 INCLUDE_GUARD
  set(GUARD_TEXT "${TARGET}_${SUB_DIR}_H")
  string(REPLACE "." "_" GUARD_TEXT ${GUARD_TEXT})
  string(TOUPPER ${GUARD_TEXT} GUARD_TEXT)

  ## 3.2 文件头
  file(WRITE ${HEADER_FILE} "// AUTO GENERATED BY CMAKE, DO NOT MODIFY!!\n"
    "#if !defined(${GUARD_TEXT})\n"
    "#define ${GUARD_TEXT}\n\n"
    "#include \"sdk_config.h\"\n\n"
    "#include <stddef.h>\n\n"
    "#if __cplusplus\n"
    "extern \"C\" {\n"
    "#endif\n"
  )

  ## 3.3 实现文件
  set(FUNC_NAME "init_${TARGET}_${SUB_DIR}")
  string(REPLACE "." "_" FUNC_NAME ${FUNC_NAME})
  string(TOLOWER ${FUNC_NAME} FUNC_NAME)
  set(FUNC_NAME "${FUNC_NAME}")
  # message(STATUS ${FUNC_NAME})
  file(WRITE ${INIT_FILE}
    "// AUTO GENERATED BY CMAKE, DO NOT MODIFY!!\n"
    "#include <resource/resource.hpp>\n"
    "#include \"resource_${SUB_DIR}.hpp\"\n"
    "\n"
    "\n"
    "void ${FUNC_NAME}(embedded_resource *er)\n"
    "{\n"
  )

  ## 3.4 清单文件
  file(WRITE ${LIST_FILE} "${ROOT}\n")

  ## 3.5 填写头文件和清单文件，创建空白源文件
  set(RES_C_FILES)
  foreach(RES_FILE ${RES_FILES})
    ### 3.5.1 头文件内容
    file(RELATIVE_PATH REL_PATH ${ROOT} ${RES_FILE})
    string(REPLACE "/" "_" VAR_PREFIX ${REL_PATH})
    string(REPLACE "." "_" VAR_PREFIX ${VAR_PREFIX})
    string(REPLACE " " "_" VAR_PREFIX ${VAR_PREFIX})
    file(APPEND ${HEADER_FILE}
            "extern const char          ${VAR_PREFIX}_name[];\n"
            "extern const unsigned char ${VAR_PREFIX}_data[];\n"
            "extern const size_t        ${VAR_PREFIX}_length;\n"
            "extern const size_t        ${VAR_PREFIX}_origin_length;\n"
    )
    ### 3.5.2 清单文件内容
    set(IMPL_FILE ${RESOURCE_DIR}/${VAR_PREFIX}.c)
    file(APPEND ${LIST_FILE} "${REL_PATH}:${IMPL_FILE}\n")
    ### 3.5.3 实现文件内容
    file(APPEND ${INIT_FILE}
      "    er->register_resource(${VAR_PREFIX}_name, ${VAR_PREFIX}_data, ${VAR_PREFIX}_length, ${VAR_PREFIX}_origin_length);\n"
    )
    ### 3.5.4
    # file(TOUCH ${IMPL_FILE})
    list(APPEND RES_C_FILES ${IMPL_FILE})
  endforeach()
  ## 3.5 文件尾
  file(APPEND ${HEADER_FILE}
          "#if __cplusplus\n"
          "}\n"
          "#endif\n"
          "#endif // ${GUARD_TEXT}\n"
          )
  file(APPEND ${INIT_FILE}
    "}\n\n"
  )

  # 4. 添加资源编译命令
  add_custom_command(
    OUTPUT ${RESOURCE_DIR}/prcc.done ${RES_C_FILES}
    COMMAND ${PRCC_COMMAND} ARGS ${LIST_FILE}
    COMMAND ${CMAKE_COMMAND} -E touch ${RESOURCE_DIR}/prcc.done
    COMMENT "Build Resource For ${TARGET}/${SUB_DIR}"
    DEPENDS ${RES_FILES} ${PRCC_COMMAND}
  )
  add_custom_target(
    RESOURCE_${TARGET}_${SUB_DIR}
    COMMENT "Need build resources"
    DEPENDS ${RESOURCE_DIR}/prcc.done ${RES_C_FILES}
  )
  set_property(TARGET RESOURCE_${TARGET}_${SUB_DIR} PROPERTY FOLDER "PRCC Generated")
  add_dependencies(${TARGET} RESOURCE_${TARGET}_${SUB_DIR})

  # 5. 添加源文件到目标
  target_sources(${TARGET} PRIVATE ${RES_C_FILES} ${INIT_FILE})

  # 6. 注册初始化定义
  list(APPEND CURRENT_DECLARES ${FUNC_NAME})
  set_property(TARGET ${TARGET} PROPERTY RESOURCE_INIT_DECLARES ${CURRENT_DECLARES})
endfunction()

function(add_resource_folders TARGET ROOT)
  set_property(TARGET ${TARGET} PROPERTY RESOURCE_INIT_DECLARES "")
  foreach(DIR ${ARGN})
    message(STATUS "Add Folder ${DIR} For Target ${TARGET}")
    add_resource(${TARGET} ${ROOT} ${DIR})
  endforeach()
  get_target_property(DECLARES ${TARGET} RESOURCE_INIT_DECLARES)
  # message(${DECLARES})
  # 填写资源初始化函数
  get_target_property(BIN_DIR ${TARGET} BINARY_DIR)
  set(RESOURCE_INIT_FILE ${BIN_DIR}/${TARGET}/resource_init_all.cpp)
  file(WRITE ${RESOURCE_INIT_FILE}
  "// AUTO GENERATED BY CMAKE, DO NOT MODIFY!!\n"
  "#include <resource/resource.hpp>\n"
  "\n\n"
  "DEFINE_RESOURCE_IMPL\n"
  "\n"
  )
  foreach(DECLARE ${DECLARES})
    file(APPEND ${RESOURCE_INIT_FILE}
    "extern void ${DECLARE}(embedded_resource*);\n"
    )
  endforeach()
  file(APPEND ${RESOURCE_INIT_FILE}
    "\nvoid init_module_resource(embedded_resource *inst)\n"
    "{\n"
  )
  foreach(DECLARE ${DECLARES})
    file(APPEND ${RESOURCE_INIT_FILE}
    "    ${DECLARE}(inst);\n"
    )
  endforeach()
  file(APPEND ${RESOURCE_INIT_FILE}
  "}\n\n"
  )

  # 添加依赖
  if (NOT CMAKE_CROSSCOMPILING)
    add_dependencies(${TARGET} ${PRCC_COMMAND})
  endif()

  # 添加到构建
  target_sources(${TARGET} PRIVATE ${RESOURCE_INIT_FILE})
endfunction()

# 包含 HasContentText
include(ContentSearch)

function(generate_code TARGET)
  get_target_property(BIN_DIR ${TARGET} BINARY_DIR)
  set(CODE_GEN_DIR ${BIN_DIR}/code_gen)
  get_target_property(SRC_DIR ${TARGET} SOURCE_DIR)

  # 1. 搜索需要执行代码生成的源文件
  get_target_property(SRCS ${TARGET} SOURCES)
  set(ALL_NEEDED_FILES)
  foreach(header ${SRCS})
    HasContentText(OK ${header} "GENERATED_GET_DATA")
    if (OK)
      list(APPEND ALL_NEEDED_FILES ${header})
    endif()
  endforeach()
  
  set(GENERATED_FILES)
  foreach(header ${ALL_NEEDED_FILES})
    file(RELATIVE_PATH REL_PATH ${SRC_DIR} ${header})
    set(GENERATED_FILE ${CODE_GEN_DIR}/${REL_PATH}.generated.cpp)
    list(APPEND GENERATED_FILES ${GENERATED_FILE})
    get_filename_component(GENERATED_DIR ${GENERATED_FILE} DIRECTORY)
    file(MAKE_DIRECTORY ${GENERATED_DIR})

    # message(STATUS "${header} -> ${GENERATED_FILE}")
    add_custom_command(
      OUTPUT ${GENERATED_FILE}
      # COMMAND ${CMAKE_COMMAND} -E echo ${CODEGEN_COMMAND} "${header}" "${GENERATED_FILE}"
      #   "-I$<JOIN:$<TARGET_PROPERTY:${TARGET},INCLUDE_DIRECTORIES>, -I>"
      #   "-D$<JOIN:$<TARGET_PROPERTY:${TARGET},COMPILE_DEFINITIONS>, -D>"
      #   "-H$<JOIN:$<TARGET_PROPERTY:${TARGET},PRECOMPILE_HEADERS>, -H>"
      COMMAND ${CODEGEN_COMMAND}
        ARGS "${header}" "${GENERATED_FILE}"
        "-I$<JOIN:$<TARGET_PROPERTY:${TARGET},INCLUDE_DIRECTORIES>, -I>"
        "-D$<JOIN:$<TARGET_PROPERTY:${TARGET},COMPILE_DEFINITIONS>, -D>"
        "-H$<JOIN:$<TARGET_PROPERTY:${TARGET},PRECOMPILE_HEADERS>, -H>"

      COMMAND ${CMAKE_COMMAND} -E touch ${GENERATED_FILE}
      # FIXME: NEXT LINE USED FOR DEBUG
      # COMMAND ${CMAKE_COMMAND} -E touch ${header}
      COMMENT "codegen for file ${header}"
      DEPENDS ${header} ${CODEGEN_COMMAND}
    )
  endforeach()
  
  if (GENERATED_FILES)
    target_sources(${TARGET} PRIVATE ${GENERATED_FILES})
    source_group(TREE ${CODE_GEN_DIR} PREFIX "Generated Files" FILES ${GENERATED_FILES})

    # 2. 获取编译选项（宏定义，包含路径，语言标准等命令行选项）
    # 3. 执行 codegen
    # 4. 编译生成的代码
    # 5. 编译 TARGET
    
    add_custom_target(
      ${TARGET}_CODE_GEN
      COMMENT "Code generation for target ${TARGET}"
      DEPENDS ${GENERATED_FILES}
    )
    set_property(TARGET ${TARGET} PROPERTY FOLDER "CodeGen")
    add_dependencies(
      ${TARGET} ${TARGET}_CODE_GEN
    )
  endif()
endfunction()

